#include "xh_asio.hpp"
#include "xh_coroutine.hpp"
#include <boost/asio/read.hpp>
#include <boost/asio/deadline_timer.hpp>

boost::system::error_code ASIOConnector::connect(const char* addr, const char* port)
{
	auto ptr = this->shared_from_this();
	auto cur = co_current();

	using namespace boost::asio::ip;

	boost::asio::ip::tcp::resolver resolver(m_socket.get_io_service());
	boost::asio::async_connect(m_socket, resolver.resolve({ addr, port }), [cur,ptr](boost::system::error_code ec, tcp::resolver::iterator) {
		ptr->m_connect_ec.ec = ec;
		co_resume(cur);
	});

	co_suspend;
	return this->m_connect_ec.ec;
}

boost::system::error_code ASIOConnector::connect(const boost::asio::ip::tcp::endpoint& endpoint)
{
	auto ptr = this->shared_from_this();
	auto cur = co_current();

	using namespace boost::asio::ip;

	m_socket.async_connect(endpoint, [cur, ptr](boost::system::error_code ec) {
		ptr->m_connect_ec.ec = ec;
		co_resume(cur);
	});

	co_suspend;
	return this->m_connect_ec.ec;
}

std::shared_ptr<ASIOConnection> ASIOConnector::make_connection()
{
	return std::make_shared<ASIOConnection>(std::move(m_socket));
}

std::size_t ASIOConnection::read(void* buffer, std::size_t length, boost::system::error_code* pec)
{
	auto ptr = this->shared_from_this();
	auto cur = co_current();

	// try now ---
	ptr->m_read_ec.bytes = 0;
	auto recv_len = m_socket.read_some(boost::asio::buffer((char*)buffer, length),ptr->m_read_ec.ec);
	if (recv_len > 0)
	{
		if (recv_len == length)
			return recv_len;

		length -= recv_len;
		ptr->m_read_ec.bytes += recv_len;
		buffer = &((char*)buffer)[recv_len];
	}
	// try end

	using namespace boost::asio::ip;
	boost::asio::async_read(m_socket,boost::asio::buffer((char*)buffer, length), [ptr, cur, pec](boost::system::error_code ec, std::size_t length) {
		ptr->m_read_ec.ec = ec;
		ptr->m_read_ec.bytes += length;
		if (pec != nullptr)
			*pec = ec;
		if (ec)
			ptr->m_socket.close();
		co_resume(cur);
    });

	co_suspend;
	return m_read_ec.bytes;
}

std::size_t ASIOConnection::read_some(void* buffer, std::size_t length, boost::system::error_code* pec)
{
	auto ptr = this->shared_from_this();
	auto cur = co_current();

	using namespace boost::asio::ip;

	// try now ---
	ptr->m_read_ec.bytes = 0;
	auto recv_len = m_socket.read_some(boost::asio::buffer((char*)buffer, length), ptr->m_read_ec.ec);
	if (recv_len > 0)
		return recv_len;
	// try end

    m_socket.async_read_some(boost::asio::buffer((char*)buffer, length),[ptr,cur,pec](boost::system::error_code ec, std::size_t length) {
        ptr->m_read_ec.ec = ec;
        ptr->m_read_ec.bytes = length;
        if (pec != nullptr)
            *pec = ec;
        if (ec)
            ptr->m_socket.close();
        co_resume(cur);
    });

	co_suspend;
	return m_read_ec.bytes;
}

std::size_t ASIOConnection::write(const void* buffer, std::size_t length, boost::system::error_code* pec)
{
	auto ptr = this->shared_from_this();
	auto cur = co_current();

	using namespace boost::asio::ip;

	// try now ---
	ptr->m_read_ec.bytes = 0;
	auto sent_len = m_socket.write_some(boost::asio::buffer((const char*)buffer, length), ptr->m_write_ec.ec);
	if (sent_len > 0)
	{
		if (sent_len == length)
			return sent_len;
		length -= sent_len;
		ptr->m_read_ec.bytes += sent_len;
		buffer = &((const char*)buffer)[sent_len];
	}
	// try end

	boost::asio::async_write(m_socket, boost::asio::buffer((const char*)buffer, length), [ptr, cur, pec](boost::system::error_code ec, std::size_t length) {
		ptr->m_write_ec.ec = ec;
		ptr->m_write_ec.bytes += length;
		if (pec != nullptr)
			*pec = ec;
		if (ec)
			ptr->m_socket.close();
		co_resume(cur);
	});

	co_suspend;
	return m_write_ec.bytes;
}

std::size_t ASIOConnection::write_some(const void* buffer, std::size_t length, boost::system::error_code* pec)
{
	auto ptr = this->shared_from_this();
	auto cur = co_current();

	using namespace boost::asio::ip;

	// try now ---
	ptr->m_read_ec.bytes = 0;
	auto sent_len = m_socket.write_some(boost::asio::buffer((const char*)buffer, length), ptr->m_write_ec.ec);
	if (sent_len > 0)
		return sent_len;
	// try end

	m_socket.async_write_some(boost::asio::buffer((const char*)buffer, length), [ptr, cur, pec](boost::system::error_code ec, std::size_t length) {
		ptr->m_write_ec.ec = ec;
		ptr->m_write_ec.bytes += length;
		if (pec != nullptr)
			*pec = ec;
		if (ec)
			ptr->m_socket.close();
		co_resume(cur);
	});

	co_suspend;
	return m_write_ec.bytes;
}

boost::system::error_code ASIOAcceptor::accept()
{
	auto ptr = this->shared_from_this();
	auto cur = co_current();

	using namespace boost::asio::ip;

	m_acceptor.async_accept(m_socket,[ptr,cur](boost::system::error_code ec) {
		ptr->m_accept_ec.ec = ec;
		co_resume(cur);
	});

	co_suspend;
	return m_accept_ec.ec;
}

std::shared_ptr<ASIOConnection> ASIOAcceptor::make_accepted_connection()
{
	return std::make_shared<ASIOConnection>(std::move(m_socket));
}

void ASIOUtils::sleep(boost::asio::io_service& ioservice, unsigned int millsecs)
{
	auto cur = co_current();
	auto timer = new boost::asio::deadline_timer(ioservice,boost::posix_time::millisec(millsecs));
	timer->async_wait([cur, timer](boost::system::error_code ec){
		co_resume(cur);
		delete timer;
	});
	co_suspend;
}
